package edu.cth.ai23;

import java.util.Random;

public class Transform {

    private final static int[] coordX = {0, 1, 2, 2, 2, 1, 0, 0};
    private final static int[] coordY = {0, 0, 0, 1, 2, 2, 2, 1};
    private final static int[] coordXSide = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 3, 3, 3, 3, 3, 3, 3, 3, 3, 11, 11, 11, 6, 6, 6, 5, 4, 3, 2, 2, 2, 3, 4, 5, 9, 9, 9, 5, 5, 5, 5, 5, 5, 5, 5, 5, 8, 8, 8, 5, 4, 3, 0, 0, 0, 3, 4, 5, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0};
    private final static int[] coordYSide = {3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 8, 7, 6, 5, 4, 3, 2, 1, 0, 3, 4, 5, 5, 4, 3, 2, 2, 2, 3, 4, 5, 6, 6, 6, 5, 4, 3, 0, 1, 2, 3, 4, 5, 6, 7, 8, 3, 4, 5, 8, 8, 8, 5, 4, 3, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5};

    public enum CubeMoves {

        u, d, l, r, f, b;
    }

    public enum CubeCompositeMoves {

        u, d, l, r, f, b, up, dp, lp, rp, fp, bp, u2, d2, l2, r2, f2, b2;
    }

    public static final CubeCompositeMoves[] CUBEVALUES_old = {
        CubeCompositeMoves.u, CubeCompositeMoves.d, CubeCompositeMoves.l, CubeCompositeMoves.r, CubeCompositeMoves.f,CubeCompositeMoves.b,
        CubeCompositeMoves.u2, CubeCompositeMoves.d2, CubeCompositeMoves.l2, CubeCompositeMoves.r2, CubeCompositeMoves.f2, CubeCompositeMoves.b2,
        CubeCompositeMoves.bp, CubeCompositeMoves.fp, CubeCompositeMoves.rp, CubeCompositeMoves.lp, CubeCompositeMoves.dp, CubeCompositeMoves.up};
    // G1 = <U,D,R2,L2,F2,B2>
    public static final CubeCompositeMoves[] CUBEVALUES = { 
        CubeCompositeMoves.u, CubeCompositeMoves.d, CubeCompositeMoves.u2, CubeCompositeMoves.d2, CubeCompositeMoves.r2, CubeCompositeMoves.l2, CubeCompositeMoves.f2,CubeCompositeMoves.b2, CubeCompositeMoves.dp, CubeCompositeMoves.up};
    
    	
    private static final Random RANDOM = new Random();

    public static CubeCompositeMoves randMove() {
        int value = RANDOM.nextInt(CUBEVALUES.length);
        return CUBEVALUES[value];
    }

    public static void move(CubeCompositeMoves move_type, RubiksCube cube) {
        char face[] = new char[RubiksCube.dimension * RubiksCube.dimension];
        char sideLine[] = new char[RubiksCube.dimension * 4];

        switch (move_type) {
            case u:
                face = readFace(cube, 0);
                sideLine = readSideLine(cube, 0);
                rotRow(sideLine);
                storeSideLine(cube, sideLine, 0);
                face = readFace(cube, 0);
                rotFace(face);
                storeFace(face, cube, 0);
                break;
            case l:
                face = readFace(cube, 1);
                sideLine = readSideLine(cube, 1);
                rotRow(sideLine);
                storeSideLine(cube, sideLine, 1);
                face = readFace(cube, 1);
                rotFace(face);
                storeFace(face, cube, 1);
                break;
            case f:
                face = readFace(cube, 2);
                sideLine = readSideLine(cube, 2);
                rotRow(sideLine);
                storeSideLine(cube, sideLine, 2);
                face = readFace(cube, 2);
                rotFace(face);
                storeFace(face, cube, 2);
                break;
            case r:
                face = readFace(cube, 3);
                sideLine = readSideLine(cube, 3);
                rotRow(sideLine);
                storeSideLine(cube, sideLine, 3);
                face = readFace(cube, 3);
                rotFace(face);
                storeFace(face, cube, 3);
                break;
            case b:
                face = readFace(cube, 4);
                sideLine = readSideLine(cube, 4);
                rotRow(sideLine);
                storeSideLine(cube, sideLine, 4);
                face = readFace(cube, 4);
                rotFace(face);
                storeFace(face, cube, 4);
                break;
            case d:
                face = readFace(cube, 5);
                sideLine = readSideLine(cube, 5);
                rotRow(sideLine);
                storeSideLine(cube, sideLine, 5);
                face = readFace(cube, 5);
                rotFace(face);
                storeFace(face, cube, 5);
                break;
            case u2:
                Transform.move(CubeCompositeMoves.u, cube);
                Transform.move(CubeCompositeMoves.u, cube);
                break;
            case d2:
                Transform.move(CubeCompositeMoves.d, cube);
                Transform.move(CubeCompositeMoves.d, cube);
                break;
            case l2:
                Transform.move(CubeCompositeMoves.l, cube);
                Transform.move(CubeCompositeMoves.l, cube);
                break;
            case r2:
                Transform.move(CubeCompositeMoves.r, cube);
                Transform.move(CubeCompositeMoves.r, cube);
                break;
            case f2:
                Transform.move(CubeCompositeMoves.f, cube);
                Transform.move(CubeCompositeMoves.f, cube);
                break;
            case b2:
                Transform.move(CubeCompositeMoves.b, cube);
                Transform.move(CubeCompositeMoves.b, cube);
                break;
            case up:
                Transform.move(CubeCompositeMoves.u, cube);
                Transform.move(CubeCompositeMoves.u, cube);
                Transform.move(CubeCompositeMoves.u, cube);
                break;
            case dp:
                Transform.move(CubeCompositeMoves.d, cube);
                Transform.move(CubeCompositeMoves.d, cube);
                Transform.move(CubeCompositeMoves.d, cube);
                break;
            case lp:
                Transform.move(CubeCompositeMoves.l, cube);
                Transform.move(CubeCompositeMoves.l, cube);
                Transform.move(CubeCompositeMoves.l, cube);
                break;
            case rp:
                Transform.move(CubeCompositeMoves.r, cube);
                Transform.move(CubeCompositeMoves.r, cube);
                Transform.move(CubeCompositeMoves.r, cube);
                break;
            case fp:
                Transform.move(CubeCompositeMoves.f, cube);
                Transform.move(CubeCompositeMoves.f, cube);
                Transform.move(CubeCompositeMoves.f, cube);
                break;
            case bp:
                Transform.move(CubeCompositeMoves.b, cube);
                Transform.move(CubeCompositeMoves.b, cube);
                Transform.move(CubeCompositeMoves.b, cube);
                break;

        }

    }

    private static char[] readFace(RubiksCube cube, int index) {
        int xOffset = RubiksCube.xOffsets[index];
        int yOffset = RubiksCube.yOffsets[index];

        char[] face;
        /* size should be set to 8 for 3 by 3 cubes and 4 for 2 by 2 cubes */
        int size = 8;

        face = new char[size];

        for (int i = 0; i < size; i++) {
            face[i] = cube.cubeStatus[coordX[i] + xOffset][coordY[i] + yOffset];
        }

        return face;
    }

    private static void storeFace(char[] face, RubiksCube cube, int index) {
        int xOffset = RubiksCube.xOffsets[index];
        int yOffset = RubiksCube.yOffsets[index];

        for (int i = 0; i < face.length; i++) {
            cube.cubeStatus[coordX[i] + xOffset][coordY[i] + yOffset] = face[i];
        }

    }

    private static char[] readSideLine(RubiksCube cube, int index) {
        char[] sideLine = new char[RubiksCube.dimension * 4];
        for (int i = 0; i < RubiksCube.dimension * 4; i++) {
            sideLine[i] = cube.cubeStatus[coordXSide[i + RubiksCube.dimension * 4 * index]][coordYSide[i + RubiksCube.dimension * 4 * index]];
        }

        return sideLine;
    }

    private static void storeSideLine(RubiksCube cube, char[] sideLine, int index) {
        for (int i = 0; i < RubiksCube.dimension * 4; i++) {
            cube.cubeStatus[coordXSide[i + RubiksCube.dimension * 4 * index]][coordYSide[i + RubiksCube.dimension * 4 * index]] = sideLine[i];
        }
    }

    private static void rotRow(char[] row) {

        for (int i = 0; i < RubiksCube.dimension; i++) {
            char tmp = row[0];
            for (int j = 0; j < RubiksCube.dimension * 4 - 1; j++) {
                row[j] = row[j + 1];
            }
            row[RubiksCube.dimension * 4 - 1] = tmp;
        }
    }

    private static void rotFace(char[] face) {

        for (int k = 0; k < 3; k++) {

            for (int i = 0; i < RubiksCube.dimension - 1; i++) {
                char tmp = face[0];
                for (int j = 0; j < face.length - 1; j++) {
                    face[j] = face[j + 1];
                }
                face[face.length - 1] = tmp;
            }
        }

    }

    public static char[] readRow(int nr, RubiksCube cube) {
        char row[] = new char[RubiksCube.dimension * 4];
        for (int i = 0; i < RubiksCube.dimension * 4; i++) {
            row[i] = cube.cubeStatus[i][RubiksCube.dimension + nr];
        }
        return row;
    }

    public static void storeRow(int nr, char[] face, RubiksCube cube) {
        for (int i = 0; i < RubiksCube.dimension * 4; i++) {
            cube.cubeStatus[i][RubiksCube.dimension + nr] = face[i];
        }
    }

    public static int getReverseMoveIndex(int moveIndex) {
        if (moveIndex < 2) //set to 2 when using G1, or 6 when using the full move range
            moveIndex = (Transform.CUBEVALUES.length-1) - moveIndex;
        else if (moveIndex > Transform.CUBEVALUES.length - 3) //set to 3 when using G1, or 7 when using full move range
            moveIndex = Math.abs(moveIndex - (Transform.CUBEVALUES.length - 1));


        return moveIndex;
    }

    public static void applyMovesToCube(RubiksCube rc, Integer[] moves) {

        for (int i = 0; i < moves.length; i++) {
            Transform.move(Transform.CUBEVALUES[moves[i]], rc);
        }
        
    }

    public static void reverseMovesToCube(RubiksCube rc, Integer[] moves) {

        for (int i = moves.length-1; i >= 0; i--) {

            int moveIndex = Transform.getReverseMoveIndex(moves[i]);
            //in any other case it is a 180 move, the same move again will restore the cube to its previous state
            Transform.move(Transform.CUBEVALUES[moveIndex], rc);
        }

    }
}
